using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.IO;

namespace IEeee
{
    /// <summary>
    /// Some times we are going to need to make a request on the site to pull down extra info.
    /// e.g. the contents of script files aren't kept by the browers, so we need to put together an
    /// HTTP request to get them. If I was to use the web request objects in the framework, then
    /// non of the browers session / auth cokies would be sent with the request. So, this is a
    /// class that will introp into WinInet to make the request for us. This should pull the file 
    /// from IE's local cache (if its available), if not any cookies and auth tokens should be 
    /// sent up with the request.
    /// </summary>
    class HttpRequestSession
    {

        [DllImport ("wininet.dll", SetLastError = true)]
        [return: MarshalAs (UnmanagedType.Bool)]
        static extern bool InternetCloseHandle (IntPtr hInternet);

        [DllImport ("wininet.dll", SetLastError = true)]
        static extern WinInetErrCodes InternetAttemptConnect (int dwReserved);

        [DllImport ("wininet.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern IntPtr InternetOpen (string lpszAgent, WinInetAccessType dwAccessType, string lpszProxyName,
                                           string lpszProxyBypass, WinInetFlags WinInetFlags);


        [DllImport ("wininet.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern IntPtr InternetConnect (IntPtr hInternet, string lpszServerName, short nServerPort,
                                              string lpszUsername, string lpszPassword, int dwService,
                                              int dwFlags, IntPtr dwContext);

        [DllImport ("wininet.dll", SetLastError = true)]
        static extern IntPtr HttpOpenRequest (IntPtr hConnect,
                                              [MarshalAs (UnmanagedType.LPWStr)] string pwszVerb,
                                              [MarshalAs (UnmanagedType.LPWStr)] string pwszObjectName,
                                              [MarshalAs (UnmanagedType.LPWStr)] string pwszVersion,
                                              [MarshalAs (UnmanagedType.LPWStr)] string pwszReferrer,
                                              //ref byte[] ppwszAcceptTypes,
                                              IntPtr ppwszAcceptTypes,
                                              int dwFlags,
                                              IntPtr dwContext);


        [DllImport ("wininet.dll", SetLastError = true)]
        static extern bool HttpSendRequest (IntPtr hRequest,
                                              string lpszHeaders,
                                              int dwHeadersLength,
                                              IntPtr lpOptional,
                                              int dwOptionalLength);

        [DllImport ("wininet.dll", SetLastError = true)]
        static extern bool HttpQueryInfo (IntPtr hRequest,
                                          QueryInfoFlags dwInfoLevel,
                                          byte[] lpvBuffer,
                                          ref int lpdwBufferLength,
                                          ref int lpdwIndex);


        [DllImport ("wininet.dll", SetLastError = true)]
        static extern IntPtr InternetOpenUrl (IntPtr hInternet,
                                              string lpszUrl,
                                              string lpszHeaders,
                                              int dwHeadersLength,
                                              int dwFlags,
                                              ref IntPtr dwContext);

        [DllImport ("wininet.dll", SetLastError = true)]
        static extern bool InternetReadFile (IntPtr hFile,
                                              byte [] lpBuffer,
                                              int dwNumberOfBytesToRead,
                                              ref int lpdwNumberOfBytesRead);



        private enum WinInetErrCodes : int {
            
            ERROR_SUCCESS = 0,
            ERROR_HTTP_HEADER_NOT_FOUND = 12150,

        }

        [Flags]
        private enum QueryInfoFlags : int
        {
            HTTP_QUERY_CONTENT_LENGTH  = 5,
            HTTP_QUERY_CONTENT_ENCODING = 29,
        }

        [Flags]
        private enum WinInetAccessType : int
        {
            INTERNET_OPEN_TYPE_PRECONFIG    = 0,   // use registry configuration
            INTERNET_OPEN_TYPE_DIRECT       = 1,      // direct to net
            INTERNET_OPEN_TYPE_PROXY        = 3,       // via named proxy
            INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY = 4, // prevent using java/script/INS
        }

        [Flags]
        private enum WinInetFlags : int
        {
            INTERNET_FLAG_ASYNC         = 0x10000000,
            INTERNET_FLAG_FROM_CACHE    = 0x01000000,
            INTERNET_FLAG_OFFLINE       = 0x01000000,
        }

        private const int INTERNET_DEFAULT_HTTP_PORT = 80;

        public static string GetBody (Uri url)
        {
            return GetBody (url, "IEeee");
        }

        public static string GetBody (Uri url, String userAgent)
        {

            ArgTest.IsNotNull (url, "url");
            ArgTest.IsNotNullOrEmpty (url.ToString (), "url");
            ArgTest.IsNotNullOrEmpty (userAgent, "userAgent");

            IntPtr hInternet = IntPtr.Zero;
            IntPtr hOpenedUrl = IntPtr.Zero;

            if (InternetAttemptConnect (0) != WinInetErrCodes.ERROR_SUCCESS)
            {
                throw new System.Net.WebException ("InternetAttemptConnect: Unable to connect to the get file \"" + url + "\"");
            }

            string lpszAgent = "IEeee";
            WinInetAccessType dwAccessType = WinInetAccessType.INTERNET_OPEN_TYPE_PRECONFIG;
            string lpszProxyName = null;
            string lpszProxyBypass = null;
            WinInetFlags dwFlags = 0;//            WinInetFlags.INTERNET_FLAG_FROM_CACHE;

            hInternet = InternetOpen (lpszAgent, dwAccessType, lpszProxyName, lpszProxyBypass, dwFlags);
            if (hInternet == IntPtr.Zero)
            {
                throw new Win32Exception (Marshal.GetLastWin32Error (), "InternetOpen: Unable to connect to the internet");
            }

            try
            {
                string lpszUrl = url.ToString ();
                string lpszHeaders = null;
                int dwHeadersLength = 0;
                int dwOpenFlags = 0;
                IntPtr dwOpenContext = IntPtr.Zero;

                hOpenedUrl = InternetOpenUrl (hInternet, lpszUrl, lpszHeaders, dwHeadersLength, dwOpenFlags, ref dwOpenContext);

                int initialBufferSize = 4096;


                String contentLength = GetHeader (hOpenedUrl, QueryInfoFlags.HTTP_QUERY_CONTENT_LENGTH);
                if (contentLength != null)
                {
                    int val = 0;
                    if (Int32.TryParse (contentLength, out val))
                    {
                        initialBufferSize = val;
                    }
                }

                String encodingHeader = GetHeader (hOpenedUrl, QueryInfoFlags.HTTP_QUERY_CONTENT_ENCODING);
                Encoding responseEncoding = null;
                if (encodingHeader != null)
                {
                    responseEncoding = Encoding.GetEncoding (encodingHeader);
                }
                else
                {
                    responseEncoding = Encoding.UTF8;
                }


                byte[] buffer = new byte[initialBufferSize];

                int dwNumberOfBytesToRead = buffer.Length;
                int dwNumberOfBytesRead = 0;

                int totalBytesRead = 0;

                using (MemoryStream ms = new MemoryStream ())
                {
                    bool done = false;

                    while (!done)
                    {
                        bool success = InternetReadFile (hOpenedUrl, buffer, dwNumberOfBytesToRead, ref dwNumberOfBytesRead);
                        if (success == false)
                        {
                            throw new Win32Exception (Marshal.GetLastWin32Error (), "InternetReadFile: Unable to read response from \"" + url.ToString () + "\"");
                        }
                        if (dwNumberOfBytesRead != 0)
                        {
                            ms.Write (buffer, 0, dwNumberOfBytesRead);
                            totalBytesRead += dwNumberOfBytesRead;
                        }
                        else
                        {
                            done = true;
                        }

                    }

                    return responseEncoding.GetString (ms.GetBuffer (), 0, totalBytesRead);
                }
            }
            finally
            {
                if (hOpenedUrl != IntPtr.Zero)
                {
                    if (InternetCloseHandle (hOpenedUrl) == false)
                    {
                        throw new Win32Exception (Marshal.GetLastWin32Error (), "InternetCloseHandle: could not close hOpenedUrl.");
                    }
                }
                if (hInternet != IntPtr.Zero)
                {
                    if (InternetCloseHandle (hInternet) == false)
                    {
                        throw new Win32Exception (Marshal.GetLastWin32Error (), "InternetCloseHandle: could not close hInternet.");
                    }
                }
            }
        }


        private static String GetHeader (IntPtr hRequest, QueryInfoFlags desiredHeader)
        {
            byte[] buffer = new byte[1024];
            int dwBufferLength = buffer.Length;
            int lpdwIndex = 0;
            bool success = HttpQueryInfo (hRequest, desiredHeader, buffer, ref dwBufferLength, ref lpdwIndex);
            if (success == true)
            {
                return Encoding.ASCII.GetString (buffer, 0, dwBufferLength);
            }
            else
            {
                return null;
            }
        }
    }





}
